package com.ruyuan.dfs.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.MDC;

import java.util.Random;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 线程任务调度器
 *
 * @author Sun Dasheng
 */
@Slf4j
public class DefaultScheduler {

    //Java 原生调度器
    private ScheduledThreadPoolExecutor executor;
    //调度器的线程ID
    private AtomicInteger schedulerThreadId = new AtomicInteger(0);
    //初始状态下 线程池状态是 停止状态
    private AtomicBoolean shutdown = new AtomicBoolean(true);

    //包装线程前缀
    public DefaultScheduler(String threadNamePrefix) {
        //线程数量是 2倍 说明是 IO 型任务
        this(threadNamePrefix, Runtime.getRuntime().availableProcessors() * 2, true);
    }

    public DefaultScheduler(String threadNamePrefix, int threads) {
        //设置成守护线程 防止后台杀掉
        this(threadNamePrefix, threads, true);
    }

    public DefaultScheduler(String threadNamePrefix, int threads, boolean daemon) {
        //并发 设置成 同步
        if (shutdown.compareAndSet(true, false)) {
            //使用原生调度器
            executor = new ScheduledThreadPoolExecutor(threads,
                    //几个线程 几个id
                    r -> new DefaultThread(threadNamePrefix + schedulerThreadId.getAndIncrement(), r, daemon));
            //设置是否在此执行器已 shutdown 时继续执行现有周期性任务的策略
            executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
            //设置是否在此执行器已 shutdown 时执行现有延迟任务的策略
            executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
        }
    }

    /**
     * 调度任务
     *
     * @param name 任务名称
     * @param r    任务
     */
    public void scheduleOnce(String name, Runnable r) {
        scheduleOnce(name, r, 0);
    }

    /**
     * 调度任务
     *
     * @param name  任务名称
     * @param r     任务
     * @param delay 延迟
     */
    public void scheduleOnce(String name, Runnable r, long delay) {
        schedule(name, r, delay, 0, TimeUnit.MILLISECONDS);
    }

    /**
     * 调度任务
     *
     * @param name     任务名称
     * @param r            任务
     * @param delay    延迟执行时间
     * @param period   调度周期
     * @param timeUnit 时间单位
     */
    public void schedule(String name, Runnable r, long delay, long period, TimeUnit timeUnit) {
        if (log.isDebugEnabled()) {
            log.debug("Scheduling task {} with initial delay {} ms and period {} ms.", name, delay, period);
        }
        //如果run方法中抛出异常，而又没有捕获，会导致整个定时任务卡住。建议在整个run方法中使用try...catch...fianlly...处理。
        Runnable delegate = () -> {
            try {
                if (log.isTraceEnabled()) {
                    log.trace("Beginning execution of scheduled task {}.", name);
                }
                String loggerId = DigestUtils.md5Hex("" + System.nanoTime() + new Random().nextInt());
                MDC.put("logger_id", loggerId);
                //这是又包装了一层吗?
                r.run();
            } catch (Throwable e) {
                log.error("Uncaught exception in scheduled task {} :", name, e);
            } finally {
                if (log.isTraceEnabled()) {
                    log.trace("Completed execution of scheduled task {}.", name);
                }
                MDC.remove("logger_id");
            }
        };

        //如果关机了 就不再调用了
        if (shutdown.get()) {
            return;
        }
        //是否存在延迟
        if (period > 0) {
            //创建具有给定纳米时间和周期的周期性动作
            executor.scheduleWithFixedDelay(delegate, delay, period, timeUnit);
        } else {
            //使用给定的基于 nanoTime 的触发时间创建一次性动作
            executor.schedule(delegate, delay, timeUnit);
        }
    }

    /**
     * 定时任务和延时任务的理解
     * ScheduledExecutorService#scheduleAtFixedRate() 指的是“以固定的频率”执行，period（周期）指的是两次成功执行之间的时间。
     * 上一个任务开始的时间计时，一个period后，检测上一个任务是否执行完毕，如果上一个任务执行完毕，则当前任务立即执行，如果上一个任务没有执行完毕，则需要等上一个任务执行完毕后立即执行。
     * ScheduledExecutorService#scheduleWithFixedDelay() 指的是“以固定的延时”执行，delay（延时）指的是一次执行终止和下一次执行开始之间的延迟。
     */

    /**
     * 优雅停止
     */
    public void shutdown() {
        if (shutdown.compareAndSet(false, true)) {
            log.info("Shutdown DefaultScheduler.");
            executor.shutdown();
        }
    }
}
